Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 << zurück
Visual C# 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2005

Visual C# 2005
1.320 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-586-X
gp Kapitel 26 Datenbankzugriff mit ADO.NET
  gp 26.1 Eine kleine Einführung
  gp 26.2 Die Verbindung zu einer Datenquelle herstellen
    gp 26.2.1 Der Inhalt der Verbindungszeichenfolge
    gp 26.2.2 Die Authentifizierung
    gp 26.2.3 Das Öffnen einer Verbindung
    gp 26.2.4 Schließen einer Verbindung
    gp 26.2.5 Die Dauer des Verbindungsaufbaus
    gp 26.2.6 Eigenschaften eines »Connection«-Objekts
    gp 26.2.7 Die Ereignisse eines »Connection«-Objekts
    gp 26.2.8 Unterstützung bei Projekten mit grafischer Benutzeroberfläche
  gp 26.3 Das »Command«-Objekt
    gp 26.3.1 Erzeugen eines »Command«-Objekts
    gp 26.3.2 Ausführen des »Command«-Objekts
    gp 26.3.3 Aktionsabfragen mit »ExecuteNonQuery« absetzen
    gp 26.3.4 Auswahlabfragen mit »ExecuteReader«
    gp 26.3.5 Abfragen, die genau ein Ergebnis liefern
    gp 26.3.6 Parametrisierte Abfragen
    gp 26.3.7 Die Unterstützung des Visual Studios 2005
  gp 26.4 Der »DataAdapter« als Bindeglied zwischen Datenbank und verbindungslosen Objekten
    gp 26.4.1 Die Konstruktoren der Klasse »DataAdapter«
    gp 26.4.2 Den lokalen Datenspeicher mit der Methode »Fill« füllen
    gp 26.4.3 Abrufen von Schemainformationen
    gp 26.4.4 Die Unterstützung des Visual Studios 2005
  gp 26.5 Das »DataSet«-Objekt
    gp 26.5.1 Ein »DataSet«-Objekt erzeugen
    gp 26.5.2 Das »DataSet« füllen
    gp 26.5.3 Tabellen- und Spaltenbezeichner zuordnen
  gp 26.6 »DataTable«-Objekte
    gp 26.6.1 Die Zeilen und Spalten in einer »DataTable«
    gp 26.6.2 Mit mehreren Tabellen arbeiten
    gp 26.6.3 Änderungen an einer »DataTable« vornehmen
    gp 26.6.4 Datenausgabe in WinForms mit dem Visual Studio 2005
  gp 26.7 Aktualisieren der Datenbank
    gp 26.7.1 Aktualisieren mit dem »CommandBuilder«-Objekt
    gp 26.7.2 Manuell gesteuerte Aktualisierungen
    gp 26.7.3 Aktualisieren mit »ExecuteNonQuery«
    gp 26.7.4 Manuelles Aktualisieren mit dem DataAdapter
    gp 26.7.5 Den zu aktualisierenden Datensatz in der Datenbank suchen
    gp 26.7.6 Den Benutzer über die fehlgeschlagenen Aktualisierungen informieren
    gp 26.7.7 Die konfliktverursachenden Datenzeilen bei der Datenbank abfragen


Galileo Computing

26.6 »DataTable«-Objektdowntop


Galileo Computing

26.6.1 Die Zeilen und Spalten in einer »DataTable«  downtop

Die Struktur einer DataTable setzt sich aus Spalten und Zeilen zusammen – ähnlich wie bei einem Excel-Tabellenblatt. Welche Spalten in der DataTable enthalten sind, bestimmt das SELECT-Statement. Die Anzahl der Zeilen richtet sich nach der Anzahl der Datensätze in der entsprechenden Tabelle der Originaldatenbank unter Berücksichtigung einer Filterung mit der WHERE-Klausel.

Organisiert werden die Zeilen und Spalten einer DataTable jeweils in einer separaten Auflistung, auf die über die beiden Eigenschaften Columns und Rows zugegriffen werden kann.


public DataRowCollection Rows {get;}
public DataColumnCollection Columns {get;}

Eine DataRowCollection verwaltet Objekte vom Typ DataRow. Jedes dieser Objekte enthält den Verweis auf eine Datenzeile im Abfrageergebnis. Analog werden von einer DataColumnCollection Objekte vom Typ DataColumn verwaltet, wobei ein DataColumn-Objekt der Verweis auf eine Spalte ist. Beide Auflistungen haben die üblichen Methoden Add, Clear, Remove usw., um die einzelnen Einträge zu manipulieren. Im ersten Moment scheint es nahe zu liegen, darüber eine DataTable zu ändern und diese Änderungen in die Originaldatenbank zu übertragen. Aber ganz so einfach ist es nun doch nicht, denn dabei müssen einige Aspekte beachtet werden, auf die aber an dieser Stelle nicht eingegangen wird.

Der Zugriff auf eine Datenzeile (DataRow)

Ein DataRow-Objekt stellt den Inhalt eines Datensatzes dar und kann sowohl ausgelesen als auch geändert werden. Um in einer DataTable von einem Datensatz zum anderen zu navigieren, benutzen Sie die Eigenschaft Rows der DataTable, die ein DataRowCollection-Objekt zurückgibt und alle Datensätze enthält, die das Ergebnis der Abfrage sind. Die einzelnen DataRows sind über den Indexer der Auflistung zu erreichen, dem der Index der DataRow in der Collection übergeben wird.

Mit der folgenden Anweisung wird der Verweis auf die dritte Datenzeile in der ersten Tabelle des DataSets der Variablen row zugewiesen.


DataRow row = ds.Tables[0].Rows[2];

Eine Datenzeile nur zu referenzieren, ist sicher nicht das von Ihnen verfolgte Ziel. Vielmehr werden Sie daran interessiert sein, den Inhalt einer oder mehrerer Spalten der betreffenden Datenzeile auszuwerten. Dazu veröffentlicht die DataRow einen Indexer, dem sie entweder den Namen der Spalte, den Index der Spalte in der DataColumnCollection der DataTable (die Ordinalposition) oder die Referenz auf die gewünschte Spalte übergeben.


public object this[string spaltenbezeichner] {get; set;}
public object this[int index] {get; set;}
public object this[DataColumn spalte] {get; set;}

Der Rückgabewert ist jeweils vom Typ object und enthält die Daten der angegebenen Spalte. In der Regel dürfte eine Konvertierung in den richtigen Datentyp notwendig sein. Setzen Sie die Überladung ein, die den Spaltenbezeichner erwartet, müssen zwei Ausgangssituationen beachtet werden. Per Vorgabe setzen Sie die Spaltenbezeichner ein, die auch in der Originaldatenbank bekannt sind. Haben Sie jedoch der DataColumnMappingCollection Spaltenzuordnungen hinzugefügt, müssen Sie diese angeben.

Es gibt noch weitere Überladungen des Indexers, die als zweites Argument eine Konstante der Enumeration DataRowVersion entgegennehmen. Diese Überladungen stehen im engen Zusammenhang mit Änderungen im DataSet, die der Originaldatenbank übermittelt werden sollen. Wir werden uns damit beschäftigen, wenn wir das Thema Aktualisierung ausführlich erörtern.

Im folgenden Codefragment werden die Spalten au_lname und au_lname der authors-Tabelle abgerufen und in ein DataSet gefüllt. Anschließend wird über dem Schleifenzähler, der als Index für den Zugriff auf einen Datensatz in der DataRowCollection dient, in einer Schleife jeder Datensatz angesprochen


string strCon = "Data Source=(local);" +
                "Initial Catalog=pubs;Trusted_Connection=yes";
SqlConnection con = new SqlConnection(strCon);
string strSQL = "SELECT au_lname, au_fname FROM authors";
SqlDataAdapter da = new SqlDataAdapter(strSQL, con);
DataSet ds = new DataSet();
da.Fill(ds);
// die DataRowCollection durchlaufen und die Daten in jeder Spalte anzeigen
for(int i = 0; i < ds.Tables[0].Rows.Count; i++) {
  Console.Write("Zuname : {0,-20}", ds.Tables[0].Rows[i]["au_lname"]);
  Console.WriteLine("Vorname: {0}", ds.Tables[0].Rows[i]["au_fname"]);
}

Etwas ungewöhnlich und vielleicht nicht ganz verständlich sieht im ersten Moment der direkt hintereinander geschaltete Aufruf von zwei Indexern aus:


Console.WriteLine("Vorname: {0}", ds.Tables[0].Rows[i]["au_fname"]);

Dabei handelt es sich aber nur um die verkürzte Schreibweise der folgenden beiden Anweisungen:


DataRow row = ds.Tables[0].Rows[i];
Console.WriteLine("Vorname: {0}", row["au_lname"]);

Der Zugriff auf die Spalten einer »DataTable«

Solange die Datentabelle wie im Beispiel oben nur wenige Spalten enthält, ist gegen die Angabe des Spaltenbezeichners nichts einzuwenden. Enthält das Ergebnis der Abfrage jedoch viele Spalten oder ist die Anzahl sogar unbekannt, müssen Sie eine andere Technik anwenden, um auf alle Daten eines Datensatzes zugreifen zu können.

Unterstützung erhalten Sie dabei von der Eigenschaft Columns des DataTable-Objekts, die eine Auflistung zurückliefert, in der alle Spalten enthalten sind. Auf die einzelnen Spalten können Sie über die Angabe des Indizes in der Auflistung oder über den Spaltenbezeichner zugreifen.


public virtual DataColumn this[string spaltenbezeichner] {get;}
public virtual DataColumn this[int index] {get;}

Wollen Sie sich die Daten aller Datenzeilen und aller Spalten ausgeben lassen, codieren Sie zwei verschachtelte Schleifen: In der äußeren werden alle Datensätze durchlaufen, in der inneren alle Spalten. Sie können dabei sowohl mit der herkömmlichen for-Schleife operieren als auch mit der foreach-Schleife.

Im nächsten Codefragment wird das gezeigt. Nach dem Füllen des DataSets werden alle Datensätze unter Angabe des Spaltennamens an der Konsole ausgegeben. Dabei wird der Spaltenbezeichner von der Eigenschaft ColumnName des DataColumn-Objekts bereitgestellt.


da.Fill(ds);
DataTable tbl = ds.Tables[0];
foreach(DataRow row in tbl.Rows) {
   foreach(DataColumn column in tbl.Columns) 
      Console.WriteLine("{0,-10}: {1}", column.ColumnName, row[column]);
   Console.WriteLine();
}


Galileo Computing

26.6.2 Mit mehreren Tabellen arbeiten  downtop

Bisher haben wir immer nur eine Tabelle im DataSet betrachtet. Das entspricht aber nur in wenigen Fällen den üblichen Anforderungen. Um beispielsweise die Frage zu beantworten, welche Titel ein bestimmter Autor herausgebracht hat, sind drei Tabellen notwendig: authors, titleauthor und titles. Die meisten Tabellen einer Datenbank stehen mit anderen Tabellen in Beziehung. Fast alle Beziehungen sind 1:n-Beziehungen. Beispielsweise kann ein Autor mehrere Bücher geschrieben haben.

Ein Buch kann aber auch mehrere Autoren haben. Würde man die beiden Tabellen titles und authors direkt miteinander in Beziehung setzen, entspräche das einer m:n-Beziehung. Solche Beziehungen werden üblicherweise durch zwei 1:n-Beziehungen unter Zwischenschaltung einer dritten Tabelle aufgelöst. In der Datenbank pubs ist deshalb zwischen titles und authors die Tabelle titleauthor eingefügt (siehe Abbildung 26.9).

Die Tabelle titleauthor verfügt über einen zusammengesetzten Primärschlüssel, der aus den beiden Spalten au_id und title_id gebildet wird. Dabei handelt es sich gleichzeitig um die Primärschlüsselspalten der Tabellen authors und titles.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.9   Die Beziehungen zwischen den Tabellen » authors«, »titleauthor« und »titles«

Üblicherweise werden JOIN-Abfragen dazu benutzt, um Daten aus mehreren Tabellen in einer einzigen Abfrage abzurufen:


SELECT authors.*, titleauthor.*, titles.*
FROM authors INNER JOIN
  titleauthor ON authors.au_id = titleauthor.au_id INNER JOIN
  titles ON titleauthor.title_id = titles.title_id

JOIN-Abfragen haben einige Vorteile:

gp  Das Ergebnis lässt sich filtern.
gp  Das Resultat steht in einer Ergebnismenge
gp  JOIN-Abfragen sind anerkannter Standard.

Den Vorteilen stehen auf der anderen Seite aber auch schwerwiegende Nachteile gegenüber:

gp  Die Daten einer JOIN-Abfrage sind schwierig zu aktualisieren.
gp  JOIN-Abfragen geben redundante Daten zurück (siehe dazu auch Abbildung 26.10).

ADO.NET löst die Nachteile, die eine JOIN-Abfrage hat, auf eine eigene Art und Weise. Dazu wird die JOIN-Abfrage in Einzeltabellen aufgeteilt, die miteinander in Beziehung gesetzt werden. Mit anderen Worten: Es wird ein Teil der Originaldatenbank abgebildet. Die Beziehung zwischen zwei Tabellen wird durch ein Objekt vom Typ DataRelation beschrieben.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.10   Redundante Daten einer JOIN-Abfrage

Obschon solchermaßen strukturierte DataSets schwer zu filtern sind, überwiegen die Vorteile. So werden weniger Daten zurückgegeben als bei einer JOIN-Abfrage. Damit wird einerseits die Netzbelastung als auch die Auslastung des lokalen Speichers so gering wie möglich gehalten. Zudem ist es viel einfacher, Daten zu aktualisieren. Löschen Sie zum Beispiel einen Datensatz aus der Detailtabelle (n-Seite), möchten Sie vermutlich nicht auch gleichzeitig den entsprechenden Datensatz der Mastertabelle löschen (1-Seite). Beide Informationen sind in einer JOIN-Abfrage jedoch in einer Datenzeile zusammengefasst. Operieren Sie mit einer DataRelation zwischen zwei DataTable-Objekten, lässt sich der Datensatz aus der Detailtabelle löschen, ohne dass zwangsläufig auch die entsprechende Datenzeile der Mastertabelle gelöscht wird.

Eine »DataRelation« erzeugen

Mit Hilfe einer DataRelation werden zwei DataTable-Objekte über DataColumn-Objekte miteinander verknüpft. In einer author/titleauthor-Beziehung ist z.B. die Tabelle author das übergeordnete Element und die Tabelle titleauthor das untergeordnete Element der Beziehung. Dies ist vergleichbar mit einer Primärschlüssel-/Fremdschlüssel-Beziehung. Beziehungen werden zwischen einander entsprechenden Spalten in der übergeordneten und der untergeordneten Tabelle erstellt. Das heißt, dass der Datentyp für beide Spalten identisch sein muss.

Aus einer längeren Liste möchte ich Ihnen einen der DataRelation-Konstruktoren vorstellen.


public DataRelation(string relationName, DataColumn parentColumn,    DataColumn childColumn);

Dem ersten Parameter teilen Sie mit, unter welchem Namen die DataRelation angesprochen werden soll, der zweite Parameter erwartet die Referenz auf die übergeordnete Spalte der Mastertabelle (1-Seite), der dritte Parameter die Referenz auf die untergeordnete Spalte der Detailtabelle (n-Seite).

Nachdem eine DataRelation erzeugt worden ist, muss sie dem DataSet bekannt gegeben werden. Dazu verwaltet das DataSet eine Auflistung des Typs DataRelationCollection, deren Referenz die Eigenschaft Relations zurück gibt.

Das folgende Codefragment zeigt, wie die Beziehungen zwischen den Tabellen authors und titleauthor codiert werden.


// DataAdapter definieren
SqlDataAdapter da = new SqlDataAdapter(strSQL, con);
DataSet ds = new DataSet();
da.TableMappings.Add("Table", "Autoren");
da.TableMappings.Add("Table1", "TitelAutoren");
da.Fill(ds);
// DataRelation erzeugen
DataColumn colParent = ds.Tables["Autoren"].Columns["au_id"];
DataColumn colChild = ds.Tables["TitelAutoren"].Columns["au_id"];
DataRelation rel = new DataRelation("AutorTitelAutoren", colParent, colChild);
ds.Relations.Add(rel);

In Beziehung stehende Daten suchen

DataRelation-Objekte werden hauptsächlich dazu benutzt, um Daten, die in verschiedenen DataTable-Objekten enthalten sind, zu suchen. Zu diesem Zweck stellt eine DataRow drei Methoden zur Verfügung, die auf einer DataRelation basieren:

gp  GetChildRows
gp  GetParentRow
gp  GetParentRows

GetChildRows sucht, ausgehend von einer Datenzeile in der Mastertabelle, alle zugehörigen untergeordneten Datenzeilen in der Detailtabelle. Dazu übergeben Sie der Methode die DataRelation, die beide Tabellen miteinander verknüpft, und erhalten als Ergebnis ein DataRow-Array.


DataRow[] GetChildRows(DataRelation);

Ausgehend von der untergeordneten Zeile einer Detailtabelle ruft GetParentRow die zugehörige übergeordnete Datenzeile aus einer Mastertabelle ab. Auch dieser Methode müssen Sie die DataRelation zwischen den beiden Tabellen angeben, der Rückgabewert ist eine einzige Datenzeile.


DataRow GetParentRow(DataRelation);

Sollte zwischen zwei Tabellen eine n:m-Beziehung bestehen, können Sie die GetParentRows-Methode einsetzen.


DataRow[] GetParentRows(DataRelation);

Ich möchte Ihnen nun in einem Beispielprogramm die Benutzung der Methoden zeigen. Es sollen zu jedem Autor die von ihm veröffentlichten Titel ausgegeben werden. Ausgangspunkt dazu sind die drei Tabellen authors, titleauthor und titles, wie in Abbildung 26.9 zu sehen ist.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 26\DataRelationDemo
// --------------------------------------------------------------
static void Main(string[] args) {
  // Connection aufbauen
  string strSQL = "SELECT * FROM authors;";
  strSQL += "SELECT * FROM titleauthor;";
  strSQL += "SELECT * FROM titles";
  string strCon = "...";
  SqlConnection con = new SqlConnection(strCon);
  // DataAdapter definieren
  SqlDataAdapter da = new SqlDataAdapter(strSQL, con);
  DataSet ds = new DataSet();
  da.TableMappings.Add("Table", "Autoren");
  da.TableMappings.Add("Table1", "TitelAutoren");
  da.TableMappings.Add("Table2", "Titel");
  da.Fill(ds);
  // DataRelation erzeugen
  DataColumn colParent = ds.Tables["Autoren"].Columns["au_id"];
  DataColumn colChild = ds.Tables["TitelAutoren"].Columns["au_id"];
  DataRelation rel = new DataRelation("AutorTitelAutoren", 
                                     colParent, colChild);
  ds.Relations.Add(rel);
  colParent = ds.Tables["Titel"].Columns["title_id"];
  colChild = ds.Tables["TitelAutoren"].Columns["title_id"];
  rel = new DataRelation("TitelTitelAutoren", colParent, colChild);
  ds.Relations.Add(rel);
  // zu jedem Autor die veröffentlichten Titel anzeigen
  foreach (DataRow rowAutor in ds.Tables["Autoren"].Rows) {
    Console.WriteLine("Autor: {0}, {1}", 
                rowAutor["au_lname"], rowAutor["au_fname"]);
    foreach (DataRow rowTitelAutor in rowAutor.GetChildRows
                      (ds.Relations["AutorTitelAutoren"])) {
      DataRow rowTitle;
      rowTitle = rowTitelAutor.GetParentRow
                 (ds.Relations["TitelTitelAutoren"]);
      Console.WriteLine("Titel: {0}", rowTitle["title"]);
    }
    Console.WriteLine(new string('-', 40));
  }
  Console.ReadLine();
}

In einer äußeren foreach-Schleife werden alle Datenzeilen der Tabelle der Autoren nacheinander durchlaufen. Auf jede Datenzeile wird GetChildRows aufgerufen, die alle Datensätze in titleauthor liefert. Ausgehend von jedem einzelnen Resultat wird über GetParentRow der Titel ermittelt. Die Ausgabe des Beispiels sehen Sie in Abbildung 26.11.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.11   Ausgabe des Beispielprogramms »DataRelationDemo«


Galileo Computing

26.6.3 Änderungen an einer »DataTable« vornehmen  downtop

Sehen wir uns nun an, wie wir einer DataTable eine neue DataRow hinzufügen, eine vorhandene DataRow löschen bzw. editieren können. Um einen wichtigen Punkt gleich vorwegzunehmen: Jegliche Änderung betrifft zunächst nur das DataSet. Die Originaldatenbank weiß davon nichts. Erst zu einem späteren Zeitpunkt werden alle Änderungen »en bloc« der Datenbank übermittelt. Wir behandeln daher in diesem Abschnitt nur lokale Aktualisierungen und werden uns erst zum Ende des Kapitels der Aktualisierung der Originaldatenquelle zuwenden.

Editieren einer »DataRow«

Es gibt zwei Möglichkeiten, eine Zeile zu aktualisieren. Im einfachsten Fall weisen Sie der betreffenden Spalte nur den neuen Inhalt zu, z.B.:


ds.Tables[0].Rows[3]["au_lname"] = "Fischer";

Die Änderung wird sofort in die angegebene Spalte der entsprechenden Datenzeile geschrieben.

Die zweite Alternative puffert die Änderung. Dazu wird vor Beginn der Änderung die Methode BeginEdit auf die DataRow aufgerufen und nach Beendigung EndEdit. Sie können die eingeleitete Aktualisierung auch zurücknehmen und anstelle von EndEdit die Methode CancelEdit aufrufen. Die Zeile wird dann in den Zustand zurückversetzt, den sie vor BeginEdit hatte.


DataRow row = ds.Tables[0].Rows[3];
row.BeginEdit();
row["au_lname"] = "Fischer";
row.EndEdit();
// alternativ: row.Canceledit()

Die Pufferung der Änderung ist nicht der einzige Unterschied zwischen den beiden Aktualisierungsmöglichkeiten. Die DataTable verfügt über mehrere Ereignisse, die nur im Zusammenhang mit BeginEdit und EndEdit ausgelöst werden. Es handelt sich hierbei um

gp  RowChanging
gp  RowChanged
gp  ColumnChanging
gp  ColumnChanged

Diese Ereignisse spielen eine Rolle, wenn Änderungen an einer Datenzeile oder Spalte überprüft werden müssen. Wenn wir uns später dem Zurückschreiben der Änderungen in die Originaldatenbank zuwenden, werden wir noch einmal diese Ereignisse berücksichtigen.

Löschen einer Datenzeile

Das Löschen einer Datenzeile ist sehr einfach: Sie rufen die Methode Delete der DataRow auf, die gelöscht werden soll.


row.Delete();

Es ist falsch anzunehmen, dass die betreffende Datenzeile nun aus der DataTable entfernt wird. Sie ist immer noch vorhanden, allerdings kennzeichnet ADO.NET sie als gelöscht. Hintergrund der Markierung ist, dass das Löschen zunächst nur das aktuelle DataSet betrifft und zu einem späteren Zeitpunkt der Originaldatenbank mitgeteilt werden muss. Es wäre daher auch grundlegend falsch, eine Datenzeile mit Remove oder RemoveAt aus der DataRowCollection der Tabelle zu entfernen, denn dann findet der Aktualisierungsprozess die Datenzeile nicht mehr.

Eine neue Datenzeile hinzufügen

Eine Datenzeile zu einer DataTable hinzuzufügen, ist auch nicht schwierig. Rufen Sie dazu die Methode NewRow auf das DataTable-Objekt auf. ADO.NET erstellt daraufhin eine neue Datenzeile mit dem Schema der Tabelle. Wenn im Schema keine Standardwerte vorgegeben werden, sind die Inhalte der Spalten auf NULL gesetzt. Haben Sie alle Einträge in der neuen Zeile vorgenommen, müssen Sie die neue Zeile auch noch der DataRowCollection anhängen, denn das ist zu diesem Zeitpunkt noch nicht geschehen.


DataTable tbl = ds.Tables[0];
DataRow row = tbl.NewRow();
row["au_id"] = "123–56–6789";
row["au_lname"] = "Meier";
row["au_fname"] = "Franz";
// ...
tbl.Rows.Add(row);

Was bei einer Änderung einer Datenzeile passiert

Ein DataSet ist im lokalen Cache des Benutzers abgelegt. Während des Löschen, Änderns und Hinzufügens von Datenzeilen besteht zu der Originaldatenbank keine Verbindung. Wenn der Benutzer später die geänderten Daten an die Datenbank übermitteln möchte, muss sich das DataSet daran erinnern können, welche Zeilen von einer Änderung betroffen sind.

ADO.NET speichert diese Informationen in der Eigenschaft RowState der betreffenden Datenzeile. Die Eigenschaft wird durch einen Wert der Enumeration DataRowState beschrieben.


public DataRowState RowState {get;}

DataRowState beschreibt fünf Konstanten, die Sie der folgenden Tabelle entnehmen können.


Tabelle 26.5   Mitglieder der Enumeration »DataRowState«

Member Beschreibung
Added Die Zeile wurde einer DataRowCollection hinzugefügt.
Deleted Die Zeile wurde mit der Delete-Methode der DataRow gelöscht.
Detached Die Zeile wurde erstellt, ist jedoch nicht Teil einer DataRowCollection. Eine DataRow befindet sich in diesem Zustand, wenn sie unmittelbar nach ihrer Erstellung noch keiner Auflistung hinzugefügt wurde oder wenn sie aus einer Auflistung entfernt wurde.
Modified Die Zeile wurde geändert.
Unchanged Die Zeile wurde nicht geändert.

Nicht nur, dass jede Datenzeile dahingehend gekennzeichnet ist, welche Änderung ihr widerfahren ist (oder auch nicht), Sie können sogar den ursprünglichen Spalteninhalt auswerten. Für diesen Zweck ist der Indexer einer DataRow überladen.


public object this[string columnName, DataRowVersion version] {get;}

An Stelle des Bezeichners der Spalte im ersten Parameter können Sie hier auch den Ordinalwert (= Index) oder die Referenz auf eine DataColumn angeben.

DataRowVersion ist eine Aufzählung, mit der die gewünschte Version der betreffenden Spalte in der Datenzeile angegeben werden kann.


Tabelle 26.6   Mitglieder der Enumeration »DataRowVersion«

Member Beschreibung
Current Die Zeile enthält aktuelle Werte.
Default Die Zeile enthält einen vorgeschlagenen Wert.
Original Die Standardversion der Zeile, dem aktuellen DataRowState entsprechend.
Proposed Die Zeile enthält ihre ursprünglichen Werte.

Rufen Sie mit


row["contract"]

den Inhalt einer Spalte ab, wird immer DataRowVersion.Current ausgewertet. Das ist wichtig zu wissen, denn sollten Sie die DataRowCollection in einer Schleife durchlaufen, innerhalb der zum Beispiel auf Spalten aller geänderten Zeilen zugegriffen wird, dürfen Sie von einer gelöschten Zeile nicht DataRowVersion.Current abrufen. Sie können aber sehr wohl DataRowVersion.Original auswerten, weil eine als gelöscht markierte Datenzeile nicht aus der DataRowCollection entfernt wird.

Das nächste Beispiel zeigt Ihnen die prinzipielle Vorgehensweise. Nachdem das DataSet aus der Autorentabelle mit Daten gefüllt worden ist, wird zuerst ein weiterer Datensatz hinzugefügt. Anschließend wird die Liste der Datenzeilen durchlaufen und geprüft, ob der aktuell sich im Zugriff befindliche Autor Herr White ist. Wenn ja, wird der Zuname geändert. Sollte es sich um den Autor Ringer handeln, wird der Datensatz gelöscht.

Am Ende werden noch alle geänderten Datenzeilen gesucht und jeweils der Name des Autors ausgegeben.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 26\DataRowVersionDemo
// --------------------------------------------------------------
static void Main(string[] args) {
  // Connection aufbauen
  string strSQL = "SELECT au_id, au_lname, au_fname, contract FROM authors";
  string strCon = "...";
  SqlConnection con = new SqlConnection(strCon);
  // DataAdapter definieren
  SqlDataAdapter da = new SqlDataAdapter(strSQL, con);
  DataSet ds = new DataSet();
  da.Fill(ds);
  // DataRow hinzufügen
  DataTable tbl = ds.Tables[0];
  DataRow row = tbl.NewRow();
  row["au_id"] = "123–56–6789";
  row["au_lname"] = "Meier";
  row["au_fname"] = "Franz";
  row["contract"] = 0;
  tbl.Rows.Add(row);
  // DataRow editieren
  foreach (DataRow tempRow in tbl.Rows) {
    if (tempRow["au_lname"].ToString() == "White")
      tempRow["au_lname"] = "Yellowman";
    else if (tempRow["au_lname"].ToString() == "Ringer")
      tempRow.Delete();
  }
  // Ausgabe der geänderten Datenzeilen
  foreach (DataRow editRow in tbl.Rows)
    if (editRow.RowState == DataRowState.Added)
      Console.WriteLine("Autor {0} – Added",editRow["au_lname"]);
    else if(editRow.RowState == DataRowState.Modified)
      Console.WriteLine("Autor {0} – Modified", editRow["au_lname"]);
    else if (editRow.RowState == DataRowState.Deleted)
      Console.WriteLine("Autor {0} – Deleted", 
             editRow["au_lname", DataRowVersion.Original]);
  Console.ReadLine();
}


Galileo Computing

26.6.4 Datenausgabe in WinForms mit dem Visual Studio 2005  toptop

Im Abschnitt 26.4.4 haben Sie gelernt, wie Sie mit Hilfe der vom Visual Studio 2005 bereitgestellten Controls einen DataAdapter konfigurieren können. An dieser Stelle setzen wir an und wollen jetzt die Schritte so weit vollziehen, dass die Dateninformationen auch in dafür geeigneten Steuerelementen angezeigt werden.

Die Ausgangssituation sei die: Sie haben ein SqlDataAdapter-Steuerelement in eine WinForm gezogen, die Verbindung zur Datenbank pubs und deren Tabelle authors konfiguriert. Markieren Sie nun im Komponentenfach des Designers das DataAdapter-Steuerelement. Im unteren Bereich des Eigenschaftsfenster wird Ihnen dann der Link DataSet generieren ... angezeigt, auf den Sie klicken. Es öffnet sich ein Dialog (siehe Abbildung 26.12), den Sie ohne weitere Änderungen mit OK bestätigen können.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.12   Dialog zum Generieren eines DataSets

Sie haben jetzt ein so genanntes typisiertes DataSet erzeugt, das Ihnen beim Programmieren einer Datenbankanwendung durchaus sehr nützlich sein kann. Typisierten DataSets liegt eine Klassendefinition zugrunde, die Sie in unserem Beispiel in der Datei DataSet1.Designer.cs wiederfinden. Die Komponente dataSet11, die Sie im Komponentenfach sehen, ist die Instanz der Klasse des typisierten DataSets. Wir werden uns in diesem Buch nicht weiter mit typisierten DataSets beschäftigen, weil das den Rahmen sprengen würde.

Der Inhalt der Tabelle authors soll in einem Tabellensteuerelement angezeigt werden. Hier bietet uns das Visual Studio 2005 das Control DataGridView an, das wir in die Form ziehen. Es öffnet sich sofort eine SmartTag-Unterstützung, um das Steuerelement mit einer geeigneten Datenquelle zu verbinden, die in unserem Fall das Objekt dataSet11 ist. Öffnen Sie dazu das mit Datenquelle auswählen beschriftete Kombinationslistenfeld und navigieren zu dataSet11, wie in Abbildung 26.13 gezeigt. Sie können die Datenquelle aber auch im Eigenschaftsfenster des DataGridView einstellen, indem Sie die Eigenschaft DataSource passend einstellen.

Ein DataSet kann bekanntlich grundsätzlich mehrere Tabellen beherbergen. Das gilt auch für unser Objekt dataSet11. Da ein DataGridView aber nur den Inhalt einer DataTable anzeigen kann, müssen Sie diese noch ausdrücklich dem Steuerelement bekannt geben. Das machen Sie, indem Sie die Tabelle in der Eigenschaft DataMember angeben.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.13   Auswahl der Datenquelle

Das Ergebnis ist schon sehr positiv, denn bereits im Designer können Sie innerhalb des Tabellensteuerelements die Spaltenbezeichner der ausgewählten Tabelle erkennen. Sollten Ihnen die Bezeichner au_id, au_lname usw. nicht gefallen, das Visual Studio bietet Ihnen selbstverständlich auch die Möglichkeit an, mittels Assistenten die Spaltenbezeichner nach eigenen Wünschen einzustellen. Markieren Sie einfach das Tabellensteuerelement und öffnen über dessen Eigenschaft Columns einen Dialog, der es ermöglicht, die Vorgabe der Spaltenbezeichner für jede Spalte individuell festzulegen.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.14   Ändern der Spaltenüberschriften

Sie werden spätestens an dieser Stelle probieren, wie sich die Tabelle zur Laufzeit der Anwendung präsentiert. Das Ergebnis ist aber ernüchternd: Die Tabelle bleibt leer. Denn eine Kleinigkeit müssen wir doch noch programmieren: den Aufruf der Fill-Methode des DataSet-Objekts. Die erforderliche Anweisung kann im Konstruktor der Form codiert werden, aber erst nach dem Aufruf von InitializeComponent.


public Form1() {
  InitializeComponent();
  sqlDataAdapter1.Fill(dataSet11);
}

Zur Laufzeit könnte sich die WinForm wie in Abbildung 26.15 darstellen.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.15   Gefülltes DataGridView-Steuerelement

Per Voreinstellung lassen sich die angezeigten Daten manipulieren. Sie können also Datenzeilen hinzufügen oder löschen sowie Daten ändern. In der SmartTag-Unterstützung lassen sich diese Vorgaben beliebig einstellen. Sie können das Aktualisierungsverhalten jedoch auch im Eigenschaftsfenster mit den Eigenschaften

gp  AllowUserToAddRows
gp  AllowUserToDeleteRows
gp  ReadOnly

beeinflussen. Änderungen werden nicht sofort an die Originaldatenbank übermittelt. Hier gilt dasselbe wie schon beim Füllen des DataSets: Sie müssen die Methode Update des DataAdapters manuell codieren. Mehr dazu im Abschnitt 26.7.

Datengebundene Steuerelemente

Neben dem DataGridView ist auch eine Vielzahl weiterer Steuerelemente datenbindungsfähig. Sie unterscheiden sich voneinander durch die Eigenschaften, die Dateninformationen aus einer Datenquelle anzeigen können. Ihnen ist aber eins gemeinsam: Sie werden an eine bestimmte Spalte einer Tabelle gebunden.

Die Einstellungen nehmen Sie im Eigenschaftsfenster vor. Öffnen Sie in der linken Spalte den Eintrag (DataBindings), werden alle Eigenschaften angeboten, die sich grundsätzlich dazu eignen, Daten aus einer Datenquelle wiederzugeben. Bei der sehr häufig verwendeten TextBox handelt es sich beispielsweise um die beiden Eigenschaften Tag und Text. Ein Klick auf die Schaltfläche in der Wertespalte öffnet ein Hilfsfenster, in dem Sie die von Ihnen gewünschte Spalte der Datenquelle markieren. In Abbildung 26.16 handelt es sich um die Spalte au_lname der Tabelle authors des DataSets dataSet11.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.16   Auswahl der gebundenen Spalte

Navigieren durch die Datenzeilen einer »DataTable«

Die Navigation durch die Datenzeilen einer Tabelle in einem Tabellensteuerelement bedarf keiner besonderen Aufmerksamkeit. Etwas anders verhält es sich, wenn nur der Spaltenwert der aktuellen Datenzeile einer DataTable in den Steuerelementen angezeigt werden kann. Wir benötigen in der Form dann Navigationsschaltflächen, um eine beliebige Datenzeile anzeigen zu können. In der Regel werden dazu Schaltflächen angeboten, die das Navigieren zum ersten, zum letzten, zum vorherigen und zum nächsten Datensatz erlauben.

Um durch Datenzeilen zu navigieren, verwenden datengebundene Steuerelemente eine Instanz der Klasse CurrencyManager. Diese Instanz wird einer Tabelle oder einem DataSet zugeordnet. Das CurrencyManager-Objekt veröffentlicht die Eigenschaft Position, die dazu verwendet wird sicherzustellen, dass die einzelnen Steuerelemente Daten im gleichen Datensatz lesen und schreiben.

Über die Eigenschaft BindingContext der Form wird die Verbindung zwischen den Steuerelementen in der Form und dem CurrencyManager hergestellt, z.B.:

CurrencyManager cm = (CurrencyManager)BindingContext[dataSet11, "authors"];

Je nachdem, auf welche Navigationsschaltfläche der Anwender klickt, muss jetzt nur noch die Position-Eigenschaft entsprechend angepasst werden, beispielsweise im Click-Ereignis der Schaltfläche, mit der zur nächsten Datenzeile gesprungen werden soll mit:

cm.Position++;

Alle anderen datengebundenen Steuerelemente passen ihre Ausgabe an den Inhalt der nun aktuellen Datenzeile an.

Im folgenden Beispielprogramm ist die in Abbildung 26.17 gezeigte Form vollständig codiert, so dass mit den vier Schaltflächen zwischen den Datenzeilen einer DataTable, welche die Tabelle authors enthält, navigiert werden kann. Zwischen den Schaltflächen informiert ein Label-Steuerelement den Anwender darüber, welche Datenzeile aktuell angezeigt wird.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.17   Ausgabe des Beispielprogramms »DatengebundeneControls«


// ------------------------------------------------------
// Beispiel: ...\Kapitel 26\DatengebundeneControls
// ------------------------------------------------------
public partial class Form1 : Form {
  CurrencyManager cm;
  public Form1() {
    InitializeComponent();
    sqlDataAdapter1.Fill(dataSet11);
    cm = (CurrencyManager)BindingContext[dataSet11, "authors"];
    cm.PositionChanged += new EventHandler(cm_PositionChanged);
    DisplayPosition();
  }
  private void DisplayPosition() {
    lblPosition.Text = cm.Position + 1 + " von " + cm.Count;
  }
  private void cm_PositionChanged(object sender, EventArgs e) {
    DisplayPosition();
  }
  private void btnFirst_Click(object sender, EventArgs e) {
    cm.Position = 0;
  }
  private void btnPrevious_Click(object sender, EventArgs e) {
    cm.Position--;
  }
  private void btnNext_Click(object sender, EventArgs e) {
    cm.Position++;
  }
  private void btnLast_Click(object sender, EventArgs e) {
    cm.Position = cm.Count – 1;
  }
}

Datenbindung mit »BindingSource«

Eine weitere, etwas einfachere Möglichkeit der Datenbindung bietet die vom Visual Studio 2005 angebotene Komponente BindingSource. Diese unterstützt die Bindung von Steuerelementen in einer Form und kann als Bindeglied zwischen einer Datenquelle und den Steuerelementen angesehen werden. BindingSource ersetzt den im letzten Abschnitt beschriebenen CurrencyManager.

Nachdem Sie aus der Toolbox eine BindingSource-Komponente auf die Form gezogen haben, müssen Sie zwei Eigenschaften einstellen, um dem Objekt eindeutig die Datenquelle bekannt zu geben:

gp  DataSource
gp  DataMember

Mehrere Dialoge führen Sie automatisch zum Ziel. Nachdem Sie DataSource im Eigenschaftsfenster markiert haben, klicken Sie auf den Link Projektdatenquelle hinzufügen im Hilfsfenster der Wertespalte. Im ersten Dialogfenster des Assistenten zum Konfigurieren von Datenquellen (Abbildung 26.18) können Sie zwischen einer Datenbank, einem Webdienst und einem datenführenden Objekt auswählen. Datenbank ist dabei schon vorselektiert.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.18   Auswählen eines Datenquellentyps

Über Weiter gelangen Sie zum zweiten Dialog, in dem Sie eine bestehende Datenbankverbindung aussuchen oder eine neue erstellen können. Legen Sie über Neue Verbindung... eine neue an, wird der in Abbildung 26.1 gezeigte Dialog geöffnet, in dem Sie den Datenbankserver, die Authentifizierung und die Datenbank angeben müssen.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.19   Datenverbindung bekannt geben

Auf Basis der nun bekannten Verbindungsinformationen legen Sie im letzten Dialog des Assistenten fest, welche Datenbankobjekte das DataSet enthalten soll. Zur Auswahl stehen Tabellen, Ansichten, gespeicherte Prozeduren und Funktionen. Öffnen Sie den entsprechenden Knoten, beispielsweise den der Tabellen, sehen Sie alle Tabellen aufgelistet, die in der Datenbank definiert sind. Sie können nun auch die Spalten bestimmen, die vom DataTable-Objekt beschrieben werden sollen. Die Angabe mehrerer, auch unterschiedlicher Datenbankobjekte ist durchaus möglich.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.20   Auswahl der Datenbankobjekte

Das Fertigstellen des Assistenten bewirkt, dass der Form ein (typisiertes) DataSet hinzugefügt wird. Damit ist die Grundlage geschaffen, Sie müssen nur noch der Eigenschaft DataMember des BindingSource-Objekts die Tabelle angeben. Damit wird gleichzeitig die Instanz eines TableAdapters erzeugt. Ein TableAdapter ist ähnlich einem DataAdapter, nur bezogen auf eine DataTable. Das TableAdapter-Objekt wird namentlich eindeutig gekennzeichnet. Beschreibt es die Tabelle authors, lautet der Bezeichner authorsTableAdapter. Sie finden das Objekt im Komponentenfach.

Die Steuerelemente, die Dateninformationen anzeigen sollen, müssen jetzt mit dem BindingSource-Objekt verknüpft werden. Das geschieht ebenfalls über den Eintrag (DataBindings) im Eigenschaftsfenster des entsprechenden Elements. Allerdings wird Ihnen nun auch das BindingSource-Objekt zur Auswahl mit angeboten. Handelt es sich um ein Tabellensteuerelement, wählen Sie aus der zur Eigenschaft DataSource angebotenen Liste das Bindungsobjekt aus.

Das Steuerelement »BindingNavigator«

Eine weitere Neuerung des Visual Studios 2005 ist das Steuerelement BindingNavigator. Dabei handelt es sich um eine speziell ausgebildete Toolbar, in der nicht nur die obligatorischen Navigationsschaltflächen, sondern darüber hinaus auch eine Schaltfläche zum Hinzufügen einer Datenzeile und zum Löschen der momentan aktuellen angeboten werden. Datenanzeigende Steuerelemente, welche die gleiche Bindungsquelle benutzen, werden ihr Verhalten hinsichtlich Navigation und Editierung den Aktionen im BindingNavigator anpassen.

Ein BindingNavigator-Steuerelement kann in einen ToolStripContainer eingebettet werden. Per Vorgabe wird das Control oben ausgerichtet. Sie haben, wie bei einem ToolStrip, ferner die Möglichkeit, das Steuerelement um zusätzliche Elemente zu ergänzen. Die bereits enthaltenen können Sie mit entsprechende Eigenschaften abschalten oder sogar ganz löschen.

Damit das Control auch wie beabsichtigt seine Aufgabe erledigt, muss ihm die Datenquelle genannt werden. Geben Sie daher der Eigenschaft BindingSource das gewünschte BindingSource-Objekt an. Jetzt ist BindingNavigator so weit vorbereitet, als Bindeglied zwischen Datenquelle und den datengebundenen Steuerelementen seine Arbeit zu verrichten.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 26.21   Das Steuerelement »BindingNavigator«

Auf der Buch-CD finden Sie das zu Abbildung 26.21 gehörige Beispielprogramm BindingNavigatorDemo.

 << zurück
  
  Zum Katalog
Zum Katalog: Visual C# 2005
Visual C# 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Visual Basic 2005






 Visual Basic 2005


Zum Katalog: Java ist auch eine Insel






 Java ist auch eine
 Insel


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de